package com.lou1052.redis.jedis.config.impl;

import com.lou1052.redis.config.ClusterConfig;
import com.lou1052.redis.config.ConfigType;
import com.lou1052.redis.config.NodeConfig;
import com.lou1052.redis.config.NodeInfo;
import com.lou1052.redis.jedis.config.JedisConfig;
import com.lou1052.redis.jedis.config.PoolConfig;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;

/**
 * jedis xml 配置文件加载
 * Created with IntelliJ IDEA.
 * User: liuqing
 * Date: 2015/10/12
 * Time: 22:29
 * To change this template use File | Settings | File Templates.
 */
public class JedisXmlConfig extends JedisConfig implements InitializingBean {

    private  Document document = null;

    private String configFile = "config/redis/redis-cluster-config.xml";

    /**
     * 设置连接池信息
     * JedisPoolConfig default
     * setTestWhileIdle(true);
     * setMinEvictableIdleTimeMillis(60000);
     * setTimeBetweenEvictionRunsMillis(30000);
     * setNumTestsPerEvictionRun(-1);
     */
    private void initPoolConfig() {
        Element poolElement = (Element) document.getElementsByTagName("pool").item(0);
        int maxTotal = poolElement!=null&&poolElement.hasAttribute("maxTotal") ? Integer.parseInt(poolElement.getAttribute("maxTotal")) :50;
        int maxIdle = poolElement!=null&&poolElement.hasAttribute("maxIdle") ? Integer.parseInt(poolElement.getAttribute("maxIdle")) : 20;
        long maxWaitMillis = poolElement!=null&&poolElement.hasAttribute("maxWaitMillis") ? Long.parseLong(poolElement.getAttribute("maxWaitMillis")) : 1000;
        boolean lifo = poolElement!=null&&poolElement.hasAttribute("lifo") ? Boolean.valueOf(poolElement.getAttribute("lifo")) : true;
        boolean testOnCreate = poolElement!=null&&poolElement.hasAttribute("testOnCreate") ? Boolean.valueOf(poolElement.getAttribute("testOnCreate")) : true;
        boolean testOnBorrow = poolElement!=null&&poolElement.hasAttribute("testOnBorrow") ? Boolean.valueOf(poolElement.getAttribute("testOnBorrow")) : true;
        boolean testOnReturn = poolElement!=null&&poolElement.hasAttribute("testOnReturn") ? Boolean.valueOf(poolElement.getAttribute("testOnReturn")) : true;
        boolean testWhileIdle = poolElement!=null&&poolElement.hasAttribute("testWhileIdle") ? Boolean.valueOf(poolElement.getAttribute("testWhileIdle")) : true;
        long timeBetweenEvictionRunsMillis = poolElement!=null&&poolElement.hasAttribute("timeBetweenEvictionRunsMillis") ? Long.parseLong(poolElement.getAttribute("timeBetweenEvictionRunsMillis")) : 30000L;
        long minEvictableIdleTimeMillis = poolElement!=null&&poolElement.hasAttribute("minEvictableIdleTimeMillis") ? Long.parseLong(poolElement.getAttribute("minEvictableIdleTimeMillis")) : 60000L;
        int numTestsPerEvictionRun = poolElement!=null&&poolElement.hasAttribute("numTestsPerEvictionRun") ? Integer.parseInt(poolElement.getAttribute("numTestsPerEvictionRun")) : -1;

        PoolConfig poolConfig = new PoolConfig();
        poolConfig.setMaxTotal(maxTotal);
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMaxWaitMillis(maxWaitMillis);
        poolConfig.setLifo(lifo);
        poolConfig.setTestOnCreate(testOnCreate);
        poolConfig.setTestOnBorrow(testOnBorrow);
        poolConfig.setTestOnReturn(testOnReturn);
        poolConfig.setTestWhileIdle(testWhileIdle);
        poolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        poolConfig.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        poolConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
        this.setPoolConfig(poolConfig);
    }

    /**
     * 设置节点信息
     */
    private void initNodeConfig() {
        // get <server> tag
        NodeList serverElements = document.getElementsByTagName("server");
        int serverLen = serverElements.getLength();
        if(this.getConfigType()==ConfigType.CLUSTER&&serverLen<6){
            new IllegalArgumentException("cluster nodes >=6");
        }
        String temp_ip;
        int temp_port;
        boolean temp_needAuth;
        String temp_auth;
        String temp_name;//分片名称
        int temp_weight;//分片权重
        Set<String> nameSet = new HashSet<>();
        NodeConfig nodeConfig = new NodeConfig();
        for (int i = 0; i < serverLen; i++) {
            Element serverElement = (Element) serverElements.item(i);
            temp_ip = serverElement.hasAttribute("ip") ? serverElement.getAttribute("ip") : "127.0.0.1";
            temp_port = Integer.parseInt(serverElement.hasAttribute("port") ? serverElement.getAttribute("port") : "6379");
            temp_needAuth = serverElement.hasAttribute("needAuth") && serverElement.getAttribute("needAuth").equals("true");
            temp_auth = null;
            // need auth
            if (temp_needAuth) {
                temp_auth = serverElement.hasAttribute("auth") ? serverElement.getAttribute("auth") : null;
                if (null == temp_auth) {
                    new IllegalArgumentException("since needAuth is true,auth must be supplied!");
                }
            }
            if(this.getConfigType()==ConfigType.SHARE){
                temp_name =  serverElement.getAttribute("name");
                temp_weight = Integer.parseInt(serverElement.getAttribute("weight"));
                nameSet.add(temp_name+temp_weight);//检查null 和 相等
                nodeConfig.addNodeInfo(new NodeInfo(temp_ip, temp_port, temp_needAuth, temp_auth,temp_name,temp_weight));
            } else{
                nodeConfig.addNodeInfo(new NodeInfo(temp_ip, temp_port, temp_needAuth, temp_auth));
            }
        }
        this.setNodeConfig(nodeConfig);
    }

    /**
     * 设置集群信息
     */
    private void initClusterConfig(){
        Element clusterElement = (Element) document.getElementsByTagName("cluster").item(0);
        //BinaryJedisCluster.DEFAULT_TIMEOUT=2000;
        int timeout = clusterElement!=null&&clusterElement.hasAttribute("timeout") ? Integer.parseInt(clusterElement.getAttribute("timeout")) : 2000;
        //BinaryJedisCluster.DEFAULT_MAX_REDIRECTIONS=5;
        int maxRedirections = clusterElement!=null&&clusterElement.hasAttribute("maxRedirections") ? Integer.parseInt(clusterElement.getAttribute("maxRedirections")) : 5;
        this.setClusterConfig(new ClusterConfig(timeout, maxRedirections));
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        InputStream stream = null;
        try{
            Resource resource = new ClassPathResource(configFile);
            stream = resource.getInputStream();
            if(stream==null){
                throw new RuntimeException("load "+configFile+" failed");
            }
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            this.document  = db.parse(stream);
        }finally{
            if(null != stream)
                stream.close();
        }
        initServerType();
        initPoolConfig();
        initNodeConfig();
        initClusterConfig();
    }

    private void initServerType() {
        Element redisElement = (Element)document.getElementsByTagName("redis").item(0);
        String type = redisElement.getAttribute("type");
        switch (type){
            case "single":
                this.setConfigType(ConfigType.SINGER);
                break;
            case "share":
                this.setConfigType(ConfigType.SHARE);
                break;
            case "cluster":
                this.setConfigType(ConfigType.CLUSTER);
                break;
            default:
                throw new IllegalArgumentException("set <redis type=\"single|share|cluster\"> ！");
        }
    }

    /**
     * Getter for property 'configFile'.
     *
     * @return Value for property 'configFile'.
     */
    public String getConfigFile() {
        return configFile;
    }

    /**
     * Setter for property 'configFile'.
     *
     * @param configFile Value to set for property 'configFile'.
     */
    public void setConfigFile(String configFile) {
        this.configFile = configFile;
    }
}
